---
title: next-intl 3.22
subtitle: Incrementally moving forward
---

import StayUpdated from '@/components/StayUpdated.mdx';

# next-intl 3.22: Incrementally moving forward

<small>Oct 21, 2024 · by Jan Amann</small>

Over the past few months, a number of minor releases have been rolled out, each providing incremental improvements to `next-intl`. And while each of these releases has been an improvement on its own, they've also been part of a larger progression towards a more unified and streamlined API surface in general.

Today, another minor release was published that marks the final step of this progression: `next-intl@3.22`.

While this release is fully backwards-compatible, it includes some modern alternatives to existing APIs. Therefore, it's a good time to take a moment to review the changes and consider migrating where relevant.

**Recent improvements:**

1. [**`defineRouting`**](#define-routing): Type-safe, centralized routing configuration (introduced in `v3.18`)
2. [**`i18n/request.ts`**](#i18n-request): Streamlined file organization (introduced in `v3.19`)
3. [**`await requestLocale`**](#await-request-locale): Preparation for Next.js 15 (introduced in `v3.22`)
4. [**`createNavigation`**](#create-navigation): Streamlined navigation APIs (introduced in `v3.22`)
5. [**`setRequestLocale`**](#set-request-locale): Static rendering (marked as stable in `v3.22`)
6. [**`defaultTranslationValues`**](#default-translation-values): Deprecated in favor of a user-land pattern (introduced in `v3.22`)

Let's take a look at these changes in more detail.

## `defineRouting` [#define-routing]

Previously, configuration that was shared among the middleware and the navigation APIs required special care to be kept in sync. With the introduction of [`defineRouting`](/docs/routing/configuration#define-routing), this configuration can now be centralized in a type-safe manner:

```tsx filename="i18n/routing.ts"
import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting({
  locales: ['en-US', 'en-GB'],
  defaultLocale: 'en-US',
  localePrefix: {
    mode: 'always',
    prefixes: {
      'en-US': '/us',
      'en-GB': '/uk'
    }
  },
  pathnames: {
    '/': '/',
    '/organization': {
      'en-US': '/organization',
      'en-GB': '/organisation'
    }
  }
});
```

This `routing` config is typically defined in a central place like `i18n/routing.ts` and can be used where previously individual configuration was required:

```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';

export default createMiddleware(routing);

// ...
```

If you've used `defineRouting` previously to v3.22 and have provided middleware options as a second argument to `createMiddleware`, you can now pass these to `defineRouting` instead.

The docs have been consistently updated to reflect these changes and also suggest the creation of navigation APIs directly in `i18n/routing.ts` for simplicity. If you prefer to keep your navigation APIs separately, that's of course fine as well.

## `i18n/request.ts` [#i18n-request]

If you're using locale-based routing, you'll typically end up with two configuration files for `next-intl`:

1. Routing configuration (defined via [`defineRouting`](/docs/routing/configuration#define-routing))
2. Request configuration (defined via [`getRequestConfig`](/docs/usage/configuration#i18n-request))

While (2) was historically suggested at `i18n.ts`, the default is now changing to `i18n/request.ts` in order to streamline the organization of files and to communicate the purpose of this file more clearly:

```
└── src
    └── i18n
        ├── routing.ts (1)
        └── request.ts (2)
```

Due to this, `i18n/request.ts` is now considered the new default and is suggested throughout the docs.

If you prefer to use a custom location instead, you can do so by providing it in `next.config.mjs`:

```tsx filename="next.config.mjs"
import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin('./somewhere/else/request.ts');

// ...
```

## `await requestLocale` in `getRequestConfig` [#await-request-locale]

Next.js 15 is on the horizon and introduces a change for request APIs to [turn async](https://nextjs.org/blog/next-15-rc2#async-request-apis-breaking-change). In preparation for this change, the `locale` that is passed to [`getRequestConfig`](/docs/usage/configuration#i18n-request) has been replaced by `requestLocale` with some minor differences:

1. `requestLocale` needs to be awaited
2. The result can be `undefined`, therefore requiring a fallback
3. The `locale` should now also be returned from `getRequestConfig`

```diff
+ import {routing} from './i18n/routing';

export default getRequestConfig(async ({
-  locale
+  requestLocale
}) => {
+  // This typically corresponds to the `[locale]` segment
+  let locale = await requestLocale;

-  // Validate that the incoming `locale` parameter is valid
-  if (!routing.locales.includes(locale as any)) notFound();
+  // Ensure that the incoming locale is valid
+  if (!locale || !routing.locales.includes(locale as any)) {
+    locale = routing.defaultLocale;
+  }

  return {
+    locale,
    // ...
  };
});
```

While slightly more verbose, this change allows you to return a fallback locale for edge cases where the middleware can't provide a locale (e.g. a global country selection page at `/` or a global 404 page).

Also note that it's considered good practice to return a valid locale here in case any unknown values are encountered in the `[locale]` segment. However, you may wish to add validation in a central place like your [root layout](/docs/routing/setup#layout) to conditionally call `notFound()` in this case.

## `createNavigation` [#create-navigation]

The newly added [`createNavigation`](/docs/routing/navigation) function supersedes these previously provided APIs:

1. `createSharedPathnamesNavigation`
2. `createLocalizedPathnamesNavigation`

This new function is a reimplementation of the existing navigation functionality and unifies the API for both use cases, while also fixing a few quirks of the previous APIs. Additionally, this implementation has been updated in anticipation of Next.js 15 to run without warnings.

**Usage**

```tsx filename="i18n/routing.ts"
import {createNavigation} from 'next-intl/navigation';
import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting(/* ... */);

export const {Link, redirect, usePathname, useRouter} =
  createNavigation(routing);
```

**Migrating to `createNavigation`**

`createNavigation` is generally considered a drop-in replacement, but a few changes might be necessary:

1. `createNavigation` is expected to receive your complete routing configuration. Ideally, you define this via the [`defineRouting`](/docs/routing/configuration#define-routing) function and pass the result to `createNavigation`. The one edge case is if you're [locales aren't known at build time](/docs/routing/navigation#locales-unknown).
2. If you've used `createLocalizedPathnamesNavigation` and have [composed the `Link`](/docs/routing/navigation#link-composition) with its `href` prop, you should no longer provide the generic `Pathname` type argument.

```diff
- ComponentProps<typeof Link<Pathname>>
+ ComponentProps<typeof Link>
```

3. If you've used [`redirect`](/docs/routing/navigation#redirect), you now have to provide an explicit locale (even if it's just the [current locale](/docs/usage/configuration#locale)). The previously passed href (whether it was a string or an object) now needs to be wrapped in an object and assigned to the `href` prop. This change was necessary in [preparation for Next.js 15](https://github.com/amannn/next-intl/issues/1375).

```tsx
// Retrieving the current locale
// ... in regular components:
const locale = useLocale();
// ... in async components:
const locale = await getLocale();
```

```diff
- redirect('/about')
+ redirect({href: '/about', locale})

- redirect({pathname: '/users/[id]', params: {id: 2}})
+ redirect({href: {pathname: '/users/[id]', params: {id: 2}}, locale})
```

4. If you've used [`getPathname`](/docs/routing/navigation#getpathname) and have previously manually prepended a locale prefix, you should no longer do so—`getPathname` now takes care of this depending on your routing strategy.

```diff
- '/'+ locale + getPathname(/* ... */)
+ getPathname(/* ... */);
```

5. If you're using a combination of `localePrefix: 'as-needed'` & `domains` and you're using `getPathname`, you now need to provide a `domain` argument (see [Special case: Using `domains` with `localePrefix: 'as-needed'`](/docs/routing/configuration#domains-localeprefix-asneeded))

## `setRequestLocale` marked as stable [#set-request-locale]

In case you rely on [static rendering](/docs/routing/setup#static-rendering), you might have used the `unstable_setRequestLocale` API before. This function has now been marked as stable since it will likely remain required for the foreseeable future.

```diff
- import {unstable_setRequestLocale} from 'next-intl/server';
+ import {setRequestLocale} from 'next-intl/server';
```

Close to a year ago, I opened [discussion #58862](https://github.com/vercel/next.js/discussions/58862) in the Next.js repository. This was an attempt at starting a conversation about how Next.js could provide a way to access a user locale in Server Components without a tradeoff in ergonomics or rendering implications. While the issue has gained in popularity and currently [ranks as #2](https://github.com/vercel/next.js/discussions?discussions_q=is%3Aopen+sort%3Atop+created%3A%3E%3D2023-10-22) of the top upvoted discussions of the past year, I've unfortunately not been able to get a response from the Next.js team on this topic so far. Based on my understanding, it's certainly not an easy problem to solve, but I'd be more than happy to collaborate on this if I can.

While I'm still optimistic that we can make the `setRequestLocale` API obsolete at some point in the future, the "unstable" prefix doesn't seem to be justified anymore—especially since the API has been known to work reliably since its introduction.

## `defaultTranslationValues` (deprecated) [#default-translation-values]

[`defaultTranslationValues`](/docs/usage/configuration#default-translation-values) allow you to share global values to be used in messages across your app. The most common case are shared rich text elements (e.g. `b: (chunks) => <b>{chunks}</b>`).

However, over time this feature has shown drawbacks:

1. We can't serialize them automatically across the RSC boundary (see [#611](https://github.com/amannn/next-intl/issues/611))
2. They get in the way of type-safe arguments (see [#410](https://github.com/amannn/next-intl/issues/410))

Due to this, the feature will be deprecated and the docs now suggest a [better alternative](/docs/usage/translations#rich-text-reuse-tags):

```tsx
import {useTranslations} from 'next-intl';
import RichText from '@/components/RichText';

function AboutPage() {
  const t = useTranslations('AboutPage');
  return <RichText>{(tags) => t.rich('description', tags)}</RichText>;
}
```

## What's next?

These release notes were originally planned to be posted as part of the next major version. However, I'm really happy that it was possible to roll out all of these changes incrementally and without breaking changes.

All of these changes were inspired by many conversations with users and contributors of `next-intl`. It's quite a privileged position to be in, being able to receive feedback from so many of you, and ensuring the spectrum of i18n use cases can be served by a lean, cohesive package. A big thank you also goes out to everyone who helped to test pre-releases of this version.

With these changes out of the way, users can upgrade to modern APIs at their own pace in the v3 range. That being said, the 4.0 release is already in the works, but mostly aims to clean up deprecated functionality to ensure `next-intl` remains minimalistic.

—Jan

<StayUpdated />
